#!/usr/bin/python3

import serial

queryCmd = [0xEB, 0x90, 0xEB, 0x90, 0xEB, 0x90, 0x01, 0xA0, 0x00, 0x6F, 0x52, 0x7F]

def crc(data, crc_len):
	"""Calculates the Tracer CRC for the given data"""
	i = j = r1 = r2 = r3 = r4 = 0
	result = 0

	r1 = data[0]
	r2 = data[1]
	crc_buff = 2

	for i in range(0, crc_len - 2):
		r3 = data[crc_buff]
		crc_buff += 1

		for j in range(0, 8):
			r4 = r1
			r1 = (r1 * 2) & 0xFF

			if r2 & 0x80:
				r1 += 1
			r2 = (r2 * 2) & 0xFF

			if r3 & 0x80:
				r2 += 1
			r3 = (r3 * 2) & 0xFF

			if r4 & 0x80:
				r1 ^= 0x10
				r2 ^= 0x41

	result = (r1 << 8) | r2

	return result

class TracerData():
	def __init__(self):
		self.batt_voltage = 0
		self.pv_voltage = 0
		# [4:2] reserved; always 0
		self.load_amps = 0
		self.batt_overdischarge_voltage = 0
		self.batt_full_voltage = 0
		self.load_on = False
		self.load_overload = False
		self.load_short = False
		# data[15] reserved; always 0
		self.batt_overload = False
		self.batt_overdischarge = False
		self.batt_full = False
		self.batt_charging = False
		self.batt_temp = 0
		self.charge_current = 0

class TracerReader():
	def __init__(self, port):
		self.s = serial.Serial(port, 9600, timeout = 0.1)

	def to_float(self, two_bytes):
		# convert two bytes to a float value
		return ((two_bytes[1] << 8) | two_bytes[0]) / 100.0

	def ReadRealtimeData(self) -> TracerData:
		self.s.write(queryCmd)
		data = self.s.read(36)
		if ( len(data) != 36 ):
			return None

		if ( data[35] != 0x7F ):
			return None

		if ( data[0:6] != b'\xEB\x90\xEB\x90\xEB\x90' ):
			return None

		# check crc
		data_crc = crc(data[6:6+27] + b'\0\0', 29)
		data_crc_a = data[33] * 256 + data[34]
		if ( data_crc != data_crc_a ):
			return None

		# parse
		result = TracerData()
		result.batt_voltage = self.to_float(data[9:11])
		result.pv_voltage = self.to_float(data[11:13])
		# [4:2] reserved; always 0
		result.load_amps = self.to_float(data[15:17])
		result.batt_overdischarge_voltage = self.to_float(data[17:19])
		result.batt_full_voltage = self.to_float(data[19:21])
		result.load_on = data[21] != 0
		result.load_overload = data[22] != 0
		result.load_short = data[23] != 0
		# data[15] reserved; always 0
		result.batt_overload = data[25] != 0
		result.batt_overdischarge = data[26] != 0
		result.batt_full = data[27] != 0
		result.batt_charging = data[28] != 0
		result.batt_temp = data[29] - 30
		result.charge_current = self.to_float(data[30:32])

		return result
